//////////////////////////////////////////////////////////////////////////
#include <Application/Clock.hpp>


using namespace Application;


Clock::Clock( ClockState state )
:	mState( state )
,	mStepRate( std::chrono::milliseconds( 1000 / 60 ) )
,	mMaxStep( std::chrono::milliseconds( 500 ) )
,	mScale( 1.0f )
,	mFrame( 0 )
{
	Reset();
}


void Clock::Update()
{
	++mFrame;

	switch( mState )
	{
	case ClockState::kRunning:
		{
			TimePoint_t	now		=	Clock_t::now();
			Duration_t	realDelta;
			_Delta( now, realDelta, mDelta );

			// Clamp time changes to max stepping rate.
			if( mDelta > mMaxStep )
			{
				mDelta			=	mMaxStep;
				mCurrentTime	+=	mMaxStep;

				// Push start time up so current-start is still correct.
				mStartTime		+=	mDelta - mMaxStep;
			}
			else
			{
				mCurrentTime	=	now;
				mStartTime		+=	realDelta - mDelta;
			}
		}
		break;

	case ClockState::kPaused:
		{
			// Set delta to 0.
			mDelta		=	std::chrono::duration_cast< Duration_t >( std::chrono::seconds( 0 ) );

			// Get current time and delta.
			TimePoint_t		current	=	Clock_t::now();
			Duration_t		delta	=	current - mCurrentTime;

			// Keep shifting time up as long as we are paused.
			mStartTime	+=	delta;
			mCurrentTime+=	delta;
		}
		break;

	case ClockState::kStepping:
		{
			// Set delta to 0.
			mDelta		=	mStepRate;

			// Get current time and delta.
			TimePoint_t		current	=	Clock_t::now();
			Duration_t		delta	=	current - mCurrentTime;

			// Keep shifting time up as long as we are stepping but adjust start to reflect fixed step rate.
			mStartTime	+=	delta-mStepRate;
			mCurrentTime+=	delta;
		}
		break;
	}
}


void Clock::State( const ClockState state )
{
	mState	=	state;
}


ClockState Clock::State() const
{
	return mState;
}


void Clock::Scale( float scale )
{
	mScale	=	scale;
}


float Clock::Scale() const
{
	return mScale;
}


void Clock::StepRate( const float rate )
{
	mStepRate	=	std::chrono::duration_cast< std::chrono::microseconds >( std::chrono::milliseconds( uint64_t( rate*1000 ) ) );
}


float Clock::StepRate() const
{
	return mStepRate.count() / float( std::chrono::duration_cast< Duration_t >( std::chrono::seconds( 1 ) ).count() );
}


void Clock::MaxStep( const float maxStep )
{
	mMaxStep	=	std::chrono::duration_cast< std::chrono::microseconds >( std::chrono::milliseconds( uint64_t( maxStep*1000 ) ) );
}


float Clock::MaxStep() const
{
	return mMaxStep.count() / float( std::chrono::duration_cast< Duration_t >( std::chrono::seconds( 1 ) ).count() );
}

void Clock::Reset()
{
	mStartTime		=	Clock_t::now();
	mCurrentTime	=	mStartTime;
	mDelta			=	std::chrono::seconds( 0 );
}


uint64_t Clock::Frame()
{
	return mFrame;
}


float Clock::DeltaTime() const
{
	std::chrono::microseconds	delta	=	std::chrono::duration_cast< std::chrono::microseconds >( mDelta );
	return float( delta.count() ) / 1000000.0f;
}


float Clock::ElapsedTime() const
{
	std::chrono::microseconds	delta	=	std::chrono::duration_cast< std::chrono::microseconds >( mCurrentTime-mStartTime );
	return float( delta.count() ) / 1000000.0f;
}


void Clock::_Delta( const TimePoint_t& now, Duration_t& real, Duration_t& adj ) const
{
	real		=	now - mCurrentTime;

	std::chrono::high_resolution_clock::rep	deltaReal	=	real.count();
	std::chrono::high_resolution_clock::rep	adjusted	=	std::chrono::high_resolution_clock::rep( deltaReal * mScale );

	adj	=	Duration_t( adjusted );
}


void Clock::_CurrentDelta( TimePoint_t& current, Duration_t& delta ) const
{
	current		=	Clock_t::now();
	delta		=	mCurrentTime - current;
}


Clock::TimePoint_t Clock::Now() const
{
	return mCurrentTime;
}
